package Runde6;

public class Map {
	
	private MapElement start;
	private MapElement end;
	private int length;
	
	/**
	 * @postcondition Creates a new Map
	 */
	public Map() {
		this.start = new MapElement(null,null);
		this.end = new MapElement(null,null);
		this.length = 0;
	}
	
	/**
	 * @precondition key != null
	 * @postcondition Returns object associated to given key
	 */
	public Object get(Object key) {
		if (start.next == null) {
			return null;
		}
		
		MapElement current = start.next;
		while (current.key != null) {
			if (current.key.equals(key)) {
				return current.content;
			}
			current = current.next;
		} 
		
		return null;
	}
	
	/**
	 * @precondition key != null
	 * @postcondition removes object associated to given key
	 */
	public boolean remove(Object key)  {
		if (start.next == null) {
			return false;
		}
		
		MapElement current = start.next;
		while (current.key != null) {
			if (current.key.equals(key)) {
				if (current.prev != null) {
				current.prev.next = current.next;
				}
				if (current.next != null) {
				current.next.prev = current.prev;
				}
				current = null;
				length--;
				return true;
			}
			current = current.next;
		}
		return false;
	}
	
	/**<
	 * @precondition key != null, content != null, key/content must be the same type then start.next's key content (if not null)
	 * 												-->if not InvalidTypeException will be thrown
	 * @postcondition Adds given key/content-pair to map, if map doesn't contain it already
	 * @invariant every key in map is unique
	 */
	public boolean add(Object key, Object content) throws InvalidTypeException {
		MapElement toAdd = new MapElement(key,content);
		if (start.next == null) {
			start.next = toAdd;
			toAdd.prev = start;
			end.prev = toAdd;
			toAdd.next = end;
			length++;
			return true;
		}
		if (!(key.getClass().isInstance(start.next.key) && (content.getClass().isInstance(start.next.content) || (content.getClass().getSuperclass().isInstance(start.next.content)) && !content.getClass().equals("Object")))) {
			throw new InvalidTypeException("Add failed.");
		}
		if (contains(key)) {
			return false;
		}
		
		end.prev.next = toAdd;
		toAdd.prev = end.prev;
		toAdd.next = end;
		end.prev = toAdd;
		length++;
		return true;
	}
	
	/**
	 * @precondition key != null
	 * @postcondition returns true or false, whether given key was found in map
	 */
	public boolean contains(Object key) {
		if (start.next == null) {
			return false;
		}
		if (get(key) != null) {
			return true;
		}
		return false;
	}

	/**
	 * @postcondition returns the map's length (amount of currently saved objects)
	 */
	public int length() {
		return length;
	}
	
	/**
	 * @postcondition returns a iterator over the map
	 */
	public Iter iterator() {
		return new Iterator();
	}
	
	//MapElement-Class
	private class MapElement {
		private final Object content;
		private final Object key;
		private MapElement prev;
		private MapElement next;
		
		/**
		 * (key/object = null is allowed, because it's needed by Map constructor, add()-method does not allow null-values)
		 * @postcondition create's a new MapElement with given params
		 */
		public MapElement(Object key, Object content){
			prev = null;
			next = null;
			this.content = content;
			this.key = key;
		}
	}
	
	//Iterator-Class
	private class Iterator implements Iter {
		
		MapElement current;
		
		/**
		 * @postcondition create's a new iterator
		 */
		public Iterator() {
			current = start;
		}
		
		/**
		 * @postcondition returns true if next() != null
		 */
		public boolean hasNext() {
			if (current.next == end || current == end) {
				return false;
			}
			return true;
		}

		/**
		 * @postcondition returns true if previous() != null
		 */
		public boolean hasPrevious() {
			if (current.prev == start || current == start) {
				return false;
			}
			return true;
		}

		/**
		 * @postcondition places iterator on next element in list and returns it
		 */
		public Object next() {
			if (!hasNext() && current == end) {
				return null;
			} 
			current = current.next;
			return current.content;
		}

		/**
		 * @postcondition places iterator on previous element in list and returns it
		 */
		public Object previous() {
			if (!hasPrevious() && current == start) {
				return null;
			} 
			current = current.prev;
			return current.content;
		}
	
	}
}
